package com.breezelin.card.recyclerview;
/*
 * Created by Breeze Lin
 * 2016/11/30 10:56
 * 1linyufeng1@gmail.com
 */

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.LinearInterpolator;

/**
 * 滑动卡片控件
 */
public class CardDisplayView extends RecyclerView {

    public static final String TAG = "CardDisplayView";

    private static final int DURATION_ANIMATION = 300; // 卡片飞行动画的时长
    private static int TOUCH_SLOP; // 拖动距离有效值（用于卡片投掷
    private static int FLING_SLOP; // 拖动速度有效值（用于卡片投掷

    private CardListener mCardListener;
    private Recycler mRecycler;
    private View mFlyingCardView; // 正在飞行中的卡片（理论上只有一个
    private GestureDetector mGestureDetector; // 手势监听，对卡片可否飞出的判断更加合理
    private Rect mRect; // 用于圈定范围计算的rect，用于在测量时划定子控件的范围，以及滑动时卡片需要飞行的距离
    private Rect mChildRect; // 圈定子控件范围用的rect，使用率高
    private Matrix mMatrix;
    private float[] mLastTouchPoint; // 上个触点，用于管理卡片的拖动
    private int mActivePointerId; // 当前用于事件处理的触点

    public CardDisplayView(Context context) {
        super(context);
        init(null, -1);
    }

    public CardDisplayView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(attrs, -1);
    }

    public CardDisplayView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle);
    }

    private void init(AttributeSet attrs, int defStyle) {
        ViewConfiguration vc = ViewConfiguration.get(getContext()); // 需要从环境中取得两个常量
        TOUCH_SLOP = vc.getScaledTouchSlop();
        FLING_SLOP = vc.getScaledMinimumFlingVelocity();
        mGestureDetector = new GestureDetector(getContext(), mGestureListener);
        mRecycler = new Recycler();
        mRect = new Rect();
        mChildRect = new Rect();
        mMatrix = new Matrix();
        mLastTouchPoint = new float[]{0, 0};
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CardDisplayView);
        if (null != typedArray) {
            String layoutManagerClassName = typedArray.getString(R.styleable.CardDisplayView_cardManager);
            try {
                setLayoutManager((LayoutManager) Class.forName(layoutManagerClassName).newInstance());
            } catch (IllegalAccessException | ClassCastException | ClassNotFoundException | InstantiationException e) {
                e.printStackTrace();
                setLayoutManager(new CardLayoutManager());
            }
            typedArray.recycle();
        } else {
            setLayoutManager(new CardLayoutManager());
        }
    }

    public void setCardListener(CardListener cardListener) {
        mCardListener = cardListener;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        return super.onInterceptTouchEvent(e);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (getChildCount() == 0 || getChildCount() == 1 || null != mFlyingCardView) {
            return false;
        }

        if (mGestureDetector.onTouchEvent(event)) { // 事件被手势识别处理，直接返回
            return true;
        }

        View topCardView = getChildAt(getChildCount() - 1);
        int actionIndex;
        float[] point = new float[]{0, 0};
        float[] offset = new float[]{0, 0};

        // 此处的手势处理用于管理卡片和触点的互动，卡片飞出的触发由前边的手势拦截处理
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: // 触点产生，记录数据
                // 顶部卡片区域赋值
                topCardView.getHitRect(mChildRect);

                // 保存触点编号
                actionIndex = event.getActionIndex();
                // 获取触点位置
                point[0] = event.getX(actionIndex);
                point[1] = event.getY(actionIndex);

                // 如果触点不在卡片范围内，事件不拦截
                if (!mChildRect.contains((int) point[0], (int) point[1])) {
                    // TODO: 2016/3/27 触点不在范围内，可以拦截事件用于飞回卡片，当然也可能实现于fling动作中
                    return false;
                } else {

                    // 记录此触点
                    mLastTouchPoint[0] = point[0];
                    mLastTouchPoint[1] = point[1];
                    mActivePointerId = event.getPointerId(actionIndex);

                    // 未知用途代码块
                    float[] points = new float[]{point[0] - topCardView.getLeft(), point[1] - topCardView.getTop()};
                    topCardView.getMatrix().invert(mMatrix);
                    mMatrix.mapPoints(points);

                    // 设置顶部卡片轴心
                    topCardView.setPivotX(points[0]);
                    topCardView.setPivotY(points[1]);
                }

                break;
            case MotionEvent.ACTION_MOVE: // 触点移动，卡片联动
                actionIndex = event.findPointerIndex(mActivePointerId);
                if (actionIndex == -1) {
                    point[0] = event.getX(0);
                    point[1] = event.getY(0);
                } else {
                    point[0] = event.getX(actionIndex);
                    point[1] = event.getY(actionIndex);
                }

                // 计算移动的距离
                offset[0] = point[0] - mLastTouchPoint[0];
                offset[1] = point[1] - mLastTouchPoint[1];

                // 拖动中
                // 使顶部卡片移动到手指相应的位置
                topCardView.setX(topCardView.getX() + offset[0]);
                topCardView.setY(topCardView.getY() + offset[1]);
//                Log.d(TAG, "onTouchEvent: top card -> (" + topCardView.getX() + ", " + topCardView.getY() + ")");
                // 悬浮的卡片会带有一定的倾斜度
                topCardView.setRotation(40 * topCardView.getTranslationX() / (getWidth() / 2.f));
                // 子控件联动处理
                // 首先获取子控件相较于上一层控件的偏移值
                float percentage;
                if (Math.abs(topCardView.getTranslationX()) > Math.abs(topCardView.getTranslationY())) {
                    if (topCardView.getTranslationX() > 0) {
                        percentage = topCardView.getTranslationX() / (getWidth() - topCardView.getX() + topCardView.getTranslationX());
                    } else {
                        percentage = -topCardView.getTranslationX() / (topCardView.getX() - topCardView.getTranslationX() + topCardView.getWidth());
                    }
                } else {
                    if (topCardView.getTranslationY() > 0) {
                        percentage = topCardView.getTranslationY() / (getHeight() - topCardView.getY() + topCardView.getTranslationY());
                    } else {
                        percentage = -topCardView.getTranslationY() / (topCardView.getY() - topCardView.getTranslationY() + topCardView.getHeight());
                    }
                }
                for (int i = 0; i < getChildCount() - 1; i++) {
                    float pow = (float) Math.pow(0.9f, getChildCount() - i - 1 - percentage);
                    getChildAt(i).setScaleX(pow);
                    getChildAt(i).setScaleY(pow);
                }
                // 记录当前的触点
                mLastTouchPoint[0] = point[0];
                mLastTouchPoint[1] = point[1];
                break;
            case MotionEvent.ACTION_UP: // 触点解除，卡片归位
            case MotionEvent.ACTION_CANCEL:
                mActivePointerId = -1;
                for (int i = 0; i < getChildCount(); i++) {
                    animCard(getChildAt(i), 0.0f, 0.0f, 0.0f, topCardView.getWidth() / 2.f,
                            topCardView.getHeight() / 2.f,
                            (float) Math.pow(0.9f, getChildCount() - i - 1),
                            (float) Math.pow(0.9f, getChildCount() - i - 1));
                }
                break;
            case MotionEvent.ACTION_POINTER_UP: // 多点状态的手指移除，关注点转移
                actionIndex = event.getActionIndex();
                int pointerId = event.getPointerId(actionIndex);

                if (pointerId == mActivePointerId) {
                    int newPointerIndex = actionIndex == 0 ? 1 : 0;
                    mLastTouchPoint[0] = event.getX(newPointerIndex);
                    mLastTouchPoint[1] = event.getY(newPointerIndex);

                    mActivePointerId = event.getPointerId(newPointerIndex);
                }
                break;
        }
//        Log.d(TAG, "Last point -> (" + mLastTouchPoint[0] + ", " + mLastTouchPoint[1] + ") current point -> (" + point[0] + ", " + point[1] + ")");
        return true;
    }

    private SimpleOnGestureListener mGestureListener = new SimpleOnGestureListener() {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            // 获取当前动作的必要参数，以及需要操作的控件
            float distance = Math.max(Math.abs(e2.getX() - e1.getX()), Math.abs(e2.getY() - e1.getY()));
            float velocity = Math.max(Math.abs(velocityX), Math.abs(velocityY));
            final View topCardView = getChildCount() <= 0 ? null : getChildAt(getChildCount() - 1);
            // 如果控件存在，距离、速度合格，且横向速度大于纵向，即处理事件，丢卡片
            if (null != topCardView && distance > TOUCH_SLOP && velocity > FLING_SLOP * 3
                    && Math.abs(velocityX) > Math.abs(velocityY)) {
                mRect.set(-topCardView.getWidth() - 100, -topCardView.getHeight() - 100,
                        getWidth() + 100, getHeight() + 100); // 圈定一个范围，足以让卡片飞出画面
                float[] target = new float[]{topCardView.getX(), topCardView.getY()}; // 目标点
                float[] range = new float[]{velocityX / 10, velocityY / 10}; // 用于延伸的值
                long duration = 0L; // 飞行动画时间
                while (mRect.contains((int) target[0], (int) target[1])) {
                    // 不断将卡片向目标方向延伸，直到确认一个不在圈定范围内的目标点
                    target[0] += range[0];
                    target[1] += range[1];
                    if (duration < DURATION_ANIMATION) {
                        duration += 100; // 相应地调整时间
                    }
                }
                // 根据丢出去的方向，回调给上层
                if (null != mCardListener) {
                    if (target[0] > 0) {
                        mCardListener.onCardCollected(topCardView);
                    } else {
                        mCardListener.onCardDropped(topCardView);
                    }
                }
                // 组织动画
                ViewPropertyAnimator animator = topCardView.animate()
                        .setDuration(duration)
                        .setInterpolator(new LinearInterpolator())
                        .x(target[0])
                        .y(target[1])
                        .rotation(Math.copySign(45, velocityX));
                animator.setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        mFlyingCardView = topCardView;
                    }

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        mFlyingCardView = null;
                        getLayoutManager().removeAndRecycleView(topCardView, mRecycler);
                        topCardView.setTranslationX(0);
                        topCardView.setTranslationY(0);
                        topCardView.setRotation(0);
                    }
                });
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    animator.setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        @RequiresApi(Build.VERSION_CODES.KITKAT)
                        public void onAnimationUpdate(ValueAnimator animation) {
                            if (null != mCardListener) {
                                mCardListener.onCardMove(topCardView,
                                        (float) animation.getCurrentPlayTime()
                                                / (float) animation.getDuration());
                            }
                        }
                    });
                }
                animator.start();
                for (int i = 0; i < getChildCount() - 1; i++) {
                    animCard(getChildAt(i), 0.0f, 0.0f, 0.0f, topCardView.getWidth() / 2.f,
                            topCardView.getHeight() / 2.f,
                            (float) Math.pow(0.9f, getChildCount() - i - 2),
                            (float) Math.pow(0.9f, getChildCount() - i - 2));
                }
                return true;
            } else {
                return false;
            }
        }
    };

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

    }

    @Override
    public void scrollToPosition(int position) {
        super.scrollToPosition(position);
        getLayoutManager().removeAndRecycleAllViews(mRecycler);
        getLayoutManager().scrollToPosition(position);
    }

    private void animCard(final View cardView, float translationX, float translationY, float rotation,
                          float pivotX, float pivotY, float scaleX, float scaleY) {
        ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(cardView,
                // 飞回中心位置
                PropertyValuesHolder.ofFloat("translationX", translationX),
                PropertyValuesHolder.ofFloat("translationY", translationY),
                // 飞回时有随机的角度
                PropertyValuesHolder.ofFloat("rotation", rotation),
                // 倾斜度（事实上是0）
                PropertyValuesHolder.ofFloat("pivotX", pivotX),
                PropertyValuesHolder.ofFloat("pivotY", pivotY),
                PropertyValuesHolder.ofFloat("scaleX", scaleX),
                PropertyValuesHolder.ofFloat("scaleY", scaleY)
        ).setDuration(DURATION_ANIMATION);
        animator.setInterpolator(new AccelerateInterpolator());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                @RequiresApi(Build.VERSION_CODES.KITKAT)
                public void onAnimationUpdate(ValueAnimator animation) {
                    if (null != mCardListener) {
                        mCardListener.onCardMove(cardView,
                                (float) animation.getCurrentPlayTime()
                                        / (float) animation.getDuration());
                    }
                }
            });
        }
        animator.start();
    }

    public static class CardLayoutManager extends LayoutManager {

        private static final int MAX_CARD_CONTAIN = 5;

        private Rect mChildRect; // 子控件的范围
        private Rect mBoundRect; // 父控件的范围
        private int mGravity; // gravity值，暂定为中心，后续考虑定制

        private int mCurrentHead; // 当前的首个条目

        public CardLayoutManager() {
            mChildRect = new Rect();
            mBoundRect = new Rect();
            mGravity = Gravity.CENTER;
        }

        @Override
        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }

        @Override
        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//        detachAndScrapAttachedViews(recycler); // 在onLayoutChildren时先移除所有条目，简单防错 todo 应该是可优化的

            mBoundRect.set(0, 0, getWidth(), getHeight()); // 父控件区域确定
            // 检查是否需要添加新的条目
            if (getChildCount() < MAX_CARD_CONTAIN && mCurrentHead + getChildCount() < getItemCount()) {
                // 看一下需要插几张卡片
                int currentPages = mCurrentHead + getChildCount();
                for (int i = 0; i < getItemCount() - currentPages && getChildCount() < MAX_CARD_CONTAIN; i++) {
                    View view = recycler.getViewForPosition(currentPages + i);
                    measureChildWithMargins(view, 0, 0);
                    addView(view, 0);
                    int width = getDecoratedMeasuredWidth(view);
                    int height = getDecoratedMeasuredHeight(view);
                    Gravity.apply(mGravity, width, height, mBoundRect, mChildRect);
                    layoutDecorated(view, mChildRect.left, mChildRect.top, mChildRect.right, mChildRect.bottom);

                    // 设置层层缩放
                    float pow = (float) Math.pow(0.9f, getChildCount() - 1);
                    Log.d(TAG, "child added scale " + pow);
                    view.setScaleX(pow);
                    view.setScaleY(pow);
                }
            }
        }

        @Override
        public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) {
            if (child.equals(getChildAt(getChildCount() - 1))) { // 回收条目，累加计数
                mCurrentHead++;
            }
            super.removeAndRecycleView(child, recycler);
            // 检查是否需要添加新的条目
            if (getChildCount() < MAX_CARD_CONTAIN && mCurrentHead + getChildCount() < getItemCount()) {
                // 看一下需要插几张卡片
                int currentPages = mCurrentHead + getChildCount();
                for (int i = 0; i < getItemCount() - currentPages && getChildCount() <= MAX_CARD_CONTAIN; i++) {
                    View view = recycler.getViewForPosition(currentPages + i);
                    measureChildWithMargins(view, 0, 0);
                    addView(view, 0);
                    int width = getDecoratedMeasuredWidth(view);
                    int height = getDecoratedMeasuredHeight(view);
                    Gravity.apply(mGravity, width, height, mBoundRect, mChildRect);
                    layoutDecorated(view, mChildRect.left, mChildRect.top, mChildRect.right, mChildRect.bottom);

                    // 设置层层缩放
                    float pow = (float) Math.pow(0.9f, getChildCount() - 1);
//                Log.d(TAG, "child added scale " + pow);
                    view.setScaleX(pow);
                    view.setScaleY(pow);
                }
            }
        }

        @Override
        public void scrollToPosition(int position) {
            mCurrentHead = position;
            requestLayout();
        }
    }

    public interface CardListener {

        /**
         * 卡片在空中移动时的回调
         *
         * @param cardView 卡片控件
         * @param progress 百分比进度的形式的卡片位置回调
         */
        @RequiresApi(Build.VERSION_CODES.KITKAT)
        void onCardMove(View cardView, float progress);

        /**
         * 卡片废弃回调（左侧飞出
         *
         * @param cardView 卡片控件
         */
        void onCardDropped(View cardView);

        /**
         * 卡片保留回调（右侧飞出
         *
         * @param cardView 卡片控件
         */
        void onCardCollected(View cardView);
    }
}
